package com.ikingtech.platform.service.system.user.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ikingtech.framework.sdk.authenticate.operation.IdentityOps;
import com.ikingtech.framework.sdk.base.model.BatchParam;
import com.ikingtech.framework.sdk.base.model.PageResult;
import com.ikingtech.framework.sdk.cache.constants.CacheConstants;
import com.ikingtech.framework.sdk.context.event.SystemInitEvent;
import com.ikingtech.framework.sdk.context.event.TenantDeleteEvent;
import com.ikingtech.framework.sdk.context.event.TenantInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.enums.authenticate.SignEndpointTypeEnum;
import com.ikingtech.framework.sdk.enums.domain.DomainEnum;
import com.ikingtech.framework.sdk.enums.system.tenant.TenantStatusEnum;
import com.ikingtech.framework.sdk.enums.system.user.UserCategoryEnum;
import com.ikingtech.framework.sdk.enums.system.user.UserConfigTypeEnum;
import com.ikingtech.framework.sdk.enums.system.user.UserLockTypeEnum;
import com.ikingtech.framework.sdk.enums.system.user.UserSocialTypeEnum;
import com.ikingtech.framework.sdk.enums.system.variable.VariableEnum;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.user.api.*;
import com.ikingtech.framework.sdk.user.model.*;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.system.user.entity.*;
import com.ikingtech.platform.service.system.user.exception.UserExceptionInfo;
import com.ikingtech.platform.service.system.user.service.repository.*;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.ikingtech.framework.sdk.context.constant.SecurityConstants.DEFAULT_PASSWORD_MODIFY_TIME;
import static com.ikingtech.framework.sdk.context.security.Identity.ADMIN_USER_NAME;

/**
 * @author tie yan
 */
@RequiredArgsConstructor
@ApiController(value = "/system/user", name = "系统管理-用户管理", description = "系统管理-用户管理")
public class UserController implements UserApi {

    private final UserRepository repo;

    private final UserConfigRepository userConfigRepo;

    private final UserCategoryRepository userCategoryRepo;

    private final UserDeptRepository userDeptRepo;

    private final UserRoleRepository userRoleRepo;

    private final UserPostRepository userPostRepo;

    private final UserSocialRepository userSocialRepo;

    private final UserTenantRepository userTenantRepo;

    private final StringRedisTemplate redisTemplate;

    private final UserDeptApi userDeptApi;

    private final UserRoleApi userRoleApi;

    private final UserMenuApi userMenuApi;

    private final UserTenantApi userTenantApi;

    private final ModelConverter converter;

    private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    private final IdentityOps identityOps;

    @Override
    @OperationLog(value = "添加用户", dataId = "#_res.getData()")
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(UserDTO user) {
        // 检查用户名是否已存在
        if (this.repo.exists(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getUsername, user.getUsername()))) {
            throw new FrameworkException(UserExceptionInfo.DUPLICATE_USERNAME);
        }
        // 检查手机号是否已存在
        if (this.repo.exists(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getPhone, user.getPhone()))) {
            throw new FrameworkException(UserExceptionInfo.DUPLICATE_PHONE);
        }
        // 检查用户名是否包含空格
        if (Tools.Reg.invalidUsername(user.getUsername())) {
            throw new FrameworkException(UserExceptionInfo.INVALID_USERNAME_OR_PASSWORD);
        }

        // 创建用户实体
        UserDO entity = Tools.Bean.copy(user, UserDO.class);
        entity.setId(Tools.Id.uuid());
        if (Tools.Str.isBlank(entity.getId())) {
            entity.setId(Tools.Id.uuid());
        }
        if (Tools.Str.isBlank(entity.getNickname())) {
            entity.setNickname(user.getName());
        }
        if (null == user.getPlatformUser()) {
            entity.setPlatformUser(true);
        }
        entity.setPassword(this.defaultEncodedPassword());
        entity.setPasswordModifyTime(DEFAULT_PASSWORD_MODIFY_TIME);
        this.repo.save(entity);
        this.insertUserDept(entity.getId(), user.getDeptIds());
        this.insertUserPost(entity.getId(), user.getPostIds());
        this.insertUserRole(entity.getId(), user.getRoleIds());
        this.insertUserTenant(entity.getId());
        this.insertUserCategory(entity.getId());
        return R.ok(entity.getId());
    }

    private String defaultEncodedPassword() {
        // 获取系统变量中的默认密码并加密
        return this.passwordEncoder.encode(VariableEnum.resolveUserDefaultPassword(this.redisTemplate.opsForHash().get(CacheConstants.SYSTEM_VARIABLE, VariableEnum.USER_DEFAULT_PASSWORD.name())));
    }

    @Override
    @OperationLog(value = "删除用户")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String id) {
        // 根据用户id获取用户实体
        UserDO entity = this.repo.getById(id);
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        // 删除用户与部门、角色、职位、租户、配置的关联关系
        this.userDeptRepo.remove(Wrappers.<UserDeptDO>lambdaQuery().eq(UserDeptDO::getUserId, id).eq(Tools.Str.isNotBlank(Me.tenantCode()), UserDeptDO::getTenantCode, Me.tenantCode()));
        this.userRoleRepo.remove(Wrappers.<UserRoleDO>query().lambda().eq(UserRoleDO::getUserId, id).eq(Tools.Str.isNotBlank(Me.tenantCode()), UserRoleDO::getTenantCode, Me.tenantCode()));
        this.userPostRepo.remove(Wrappers.<UserPostDO>query().lambda().eq(UserPostDO::getUserId, id).eq(Tools.Str.isNotBlank(Me.tenantCode()), UserPostDO::getTenantCode, Me.tenantCode()));
        this.userTenantRepo.remove(Wrappers.<UserTenantDO>lambdaQuery().eq(UserTenantDO::getUserId, id).eq(Tools.Str.isNotBlank(Me.tenantCode()), UserTenantDO::getTenantCode, Me.tenantCode()));
        this.userConfigRepo.remove(Wrappers.<UserConfigDO>lambdaQuery().eq(UserConfigDO::getUserId, id).eq(UserConfigDO::getTenantCode, Me.tenantCode()));
        this.userSocialRepo.remove(Wrappers.<UserSocialDO>lambdaQuery().eq(UserSocialDO::getUserId, id));
        if (DomainEnum.PLATFORM.name().equals(Me.domainCode())) {
            // 删除用户实体
            this.repo.removeById(id);
        }
        return R.ok();
    }

    @Override
    @OperationLog(value = "更新用户")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> update(UserDTO user) {
        if (Tools.Reg.invalidUsername(user.getUsername())) {
            throw new FrameworkException(UserExceptionInfo.INVALID_USERNAME_OR_PASSWORD);
        }
        this.updateUser(user);
        this.updateUserDept(user.getId(), user.getDeptIds());
        this.updateUserPost(user.getId(), user.getPostIds());
        this.updateUserRole(user.getId(), user.getRoleIds());
        this.updateUserCategory(user.getId());
        return R.ok();
    }

    @Override
    @OperationLog(value = "更新用户基本信息")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> updateUserInfo(UserBasicDTO user) {
        if (Tools.Reg.invalidUsername(user.getUsername())) {
            throw new FrameworkException(UserExceptionInfo.INVALID_USERNAME_OR_PASSWORD);
        }
        this.updateUser(user);
        return R.ok();
    }

    @Override
    public R<List<UserBasicDTO>> allInfo() {
        return R.ok(this.converter.modelInfoConvert(this.repo.list(Wrappers.<UserDO>lambdaQuery().ne(UserDO::getAdminUser, true))));
    }

    @Override
    public R<List<UserDTO>> page(UserQueryParamDTO queryParam) {
        List<String> userIds = new ArrayList<>();
        List<String> dataScope = new ArrayList<>();
        if (!Me.isAdmin()) {
            dataScope = Boolean.TRUE.equals(queryParam.getCurrentDeptGradeOnly()) &&
                    Tools.Str.isNotBlank(queryParam.getDeptId()) ?
                    Tools.Coll.newList(queryParam.getDeptId()) : Me.dataScope(queryParam.getDeptId());
            if (Me.invalidDataScope(dataScope)) {
                return R.ok(Collections.emptyList());
            }
        } else {
            if (Boolean.TRUE.equals(queryParam.getCurrentDeptGradeOnly())) {
                dataScope = Tools.Coll.newList(queryParam.getDeptId());
            } else {
                if (Tools.Str.isNotBlank(queryParam.getDeptId())) {
                    dataScope = this.userDeptApi.loadSubAllId(queryParam.getDeptId());
                }
            }
        }
        if (Tools.Coll.isNotBlank(dataScope)) {
            userIds = Tools.Coll.intersection(this.userDeptRepo.listObjs(Wrappers.<UserDeptDO>lambdaQuery()
                            .select(UserDeptDO::getUserId)
                            .in(UserDeptDO::getDeptId, dataScope)
                            .in(Tools.Str.isNotBlank(Me.tenantCode()), UserDeptDO::getTenantCode, Me.tenantCode())),
                    userIds, true);
            if (Tools.Coll.isBlank(userIds)) {
                return R.ok(Collections.emptyList());
            }
        }

        if (Tools.Str.isNotBlank(queryParam.getRoleId())) {
            userIds = Tools.Coll.intersection(this.userRoleRepo.listObjs(Wrappers.<UserRoleDO>lambdaQuery()
                            .select(UserRoleDO::getUserId)
                            .eq(UserRoleDO::getRoleId, queryParam.getRoleId())
                            .eq(Tools.Str.isNotBlank(Me.tenantCode()), UserRoleDO::getTenantCode, Me.tenantCode())),
                    userIds, true);
            if (Tools.Coll.isBlank(userIds)) {
                return R.ok(Collections.emptyList());
            }
        }

        if (Tools.Str.isNotBlank(queryParam.getPostId())) {
            userIds = Tools.Coll.intersection(this.userPostRepo.listObjs(Wrappers.<UserPostDO>lambdaQuery()
                            .select(UserPostDO::getUserId)
                            .eq(UserPostDO::getPostId, queryParam.getPostId())
                            .eq(Tools.Str.isNotBlank(Me.tenantCode()), UserPostDO::getTenantCode, Me.tenantCode())),
                    userIds, true);
            if (Tools.Coll.isBlank(userIds)) {
                return R.ok(Collections.emptyList());
            }
        }

        if (DomainEnum.TENANT.name().equals(Me.domainCode())) {
            userIds = Tools.Coll.intersection(this.userTenantRepo.listObjs(Wrappers.<UserTenantDO>lambdaQuery()
                            .select(UserTenantDO::getUserId)
                            .eq(UserTenantDO::getTenantCode, Me.tenantCode())),
                    userIds, true);
            if (Tools.Coll.isBlank(userIds)) {
                return R.ok(Collections.emptyList());
            }
        }

        if (DomainEnum.PLATFORM.name().equals(Me.domainCode())) {
            userIds = Tools.Coll.intersection(this.userCategoryRepo.listObjs(Wrappers.<UserCategoryDO>lambdaQuery()
                            .select(UserCategoryDO::getUserId)
                            .eq(UserCategoryDO::getCategoryCode, UserCategoryEnum.PLATFORM_ADMINISTRATOR.name())),
                    userIds, true);
            if (Tools.Coll.isBlank(userIds)) {
                return R.ok(Collections.emptyList());
            }
        }

        queryParam.setAdmin(false);
        queryParam.setUserIds(userIds);
        // 根据查询参数查询用户列表
        return R.ok(PageResult.build(this.repo.page(
                new Page<>(queryParam.getPage(), queryParam.getRows()),
                UserRepository.createWrapper(queryParam)
        )).convertBatch(this.converter::modelBatchConvert));
    }

    @Override
    public R<UserDTO> detail(String id) {
        UserDO entity = this.repo.getById(id);
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        return R.ok(this.converter.modelConvert(entity));
    }

    @Override
    @OperationLog(value = "绑定用户第三方平台信息")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> bindSocial(UserSocialBindDTO userBind) {
        for (UserSocialDTO social : userBind.getSocials()) {
            if (Tools.Str.isBlank(social.getSocialNo())) {
                throw new FrameworkException(UserExceptionInfo.INVALID_SOCIAL_NO);
            }
        }

        UserDO entity = this.repo.getOne(Wrappers.<UserDO>lambdaQuery()
                .eq(UserDO::getUsername, userBind.getUsername())
                .or()
                .eq(UserDO::getPhone, userBind.getUsername())
                .or()
                .eq(UserDO::getEmail, userBind.getUsername())
                .or()
                .eq(UserDO::getIdentityNo, userBind.getUsername()));
        if (null == entity) {
            if (Boolean.TRUE.equals(userBind.getAddWhenNotFound())) {
                if (Tools.Reg.invalidUsername(userBind.getUsername())) {
                    throw new FrameworkException(UserExceptionInfo.INVALID_USERNAME_OR_PASSWORD);
                }
                entity = this.createUser(Tools.Id.uuid(),
                        Tools.Str.isNotBlank(userBind.getUsername()) ? userBind.getUsername() : userBind.getPhone(),
                        Tools.Str.isNotBlank(userBind.getName()) ? userBind.getName() : userBind.getUsername(),
                        this.defaultEncodedPassword(),
                        false);
                this.repo.save(entity);
                this.userCategoryRepo.save(this.createUserCategory(entity.getId(), UserCategoryEnum.NORMAL_USER));
            } else {
                throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
            }
        } else {
            if (Boolean.TRUE.equals(userBind.getValidatePassword()) &&
                    !passwordEncoder.matches(userBind.getPassword(), entity.getPassword())) {
                throw new FrameworkException(UserExceptionInfo.INVALID_USERNAME_OR_PASSWORD);
            }
            this.identityOps.offline(entity.getId(), SignEndpointTypeEnum.WECHAT_MINI);
        }

        String userId = entity.getId();

        List<UserSocialDO> userSocialEntities = this.userSocialRepo.list(Wrappers.<UserSocialDO>lambdaQuery()
                .ne(UserSocialDO::getUserId, userId)
                .in(UserSocialDO::getSocialType, Tools.Coll.convertList(userBind.getSocials(), UserSocialDTO::getSocialType))
                .in(UserSocialDO::getSocialId, Tools.Coll.convertList(userBind.getSocials(), UserSocialDTO::getSocialId))
                .in(UserSocialDO::getSocialNo, Tools.Coll.convertList(userBind.getSocials(), UserSocialDTO::getSocialNo)));

        if (Tools.Coll.isNotBlank(userSocialEntities)) {
            userSocialEntities.forEach(userSocialEntity -> this.identityOps.offline(userSocialEntity.getUserId(), UserSocialTypeEnum.valueOf(userSocialEntity.getSocialType()).signEndpointType));
            this.userSocialRepo.removeBatchByIds(Tools.Coll.convertList(userSocialEntities, UserSocialDO::getId));
        }

        this.userSocialRepo.saveBatch(Tools.Coll.convertList(userBind.getSocials(), social -> {
            UserSocialDO socialEntity = Tools.Bean.copy(social, UserSocialDO.class);
            socialEntity.setId(Tools.Id.uuid());
            socialEntity.setUserId(userId);
            return socialEntity;
        }));

        return R.ok();
    }

    @Override
    @OperationLog(value = "根据用户编号重置密码")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> resetPassword(String id) {
        UserDO entity = this.repo.getById(id);
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        entity.setPassword(this.defaultEncodedPassword());
        entity.setPasswordModifyTime(LocalDateTime.now());
        this.repo.updateById(entity);
        return R.ok();
    }

    @Override
    @OperationLog(value = "重置租户管理员密码")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> resetTenantAdminUserPassword(String tenantCode) {
        UserDO entity = this.repo.getOne(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getUsername, Tools.Str.format("{}_admin", tenantCode)));
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        String password = this.defaultEncodedPassword();
        entity.setPassword(password);
        entity.setPasswordModifyTime(LocalDateTime.now());
        this.repo.updateById(entity);
        return R.ok();
    }

    @Override
    @OperationLog(value = "修改密码")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> modifyPassword(UserPasswordModifyParamDTO modifyParam) {
        UserDO entity = this.repo.getById(modifyParam.getUserId());
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        if (!this.passwordEncoder.matches(modifyParam.getOldPassword(), entity.getPassword())) {
            throw new FrameworkException(UserExceptionInfo.INVALID_OLD_PASSWORD);
        }
        if (Tools.Reg.weakPassword(modifyParam.getNewPassword())) {
            throw new FrameworkException(UserExceptionInfo.WEAK_PASSWORD);
        }
        if (Tools.Str.equals(modifyParam.getOldPassword(), modifyParam.getNewPassword())) {
            throw new FrameworkException(UserExceptionInfo.PASSWORD_SAME_WITH_OLD_ONE);
        }
        entity.setPassword(this.passwordEncoder.encode(modifyParam.getNewPassword()));
        entity.setPasswordModifyTime(LocalDateTime.now());
        this.repo.updateById(entity);
        return R.ok();
    }

    @Override
    @OperationLog(value = "锁定用户")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> lock(String username) {
        UserDO entity = this.repo.getOne(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getUsername, username));
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        if (Boolean.TRUE.equals(entity.getLocked())) {
            return R.ok();
        }
        entity.setLocked(true);
        entity.setLockType(UserLockTypeEnum.PERMANENTLY_LOCK.name());
        this.repo.updateById(entity);
        this.identityOps.offline(entity.getId());
        return R.ok();
    }

    @Override
    @OperationLog(value = "解锁用户")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> unlock(String username) {
        UserDO entity = this.repo.getOne(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getUsername, username));
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        if (Boolean.FALSE.equals(entity.getLocked())) {
            return R.ok();
        }
        entity.setLocked(false);
        entity.setLockType(UserLockTypeEnum.NO_LOCK.name());
        this.repo.updateById(entity);
        return R.ok();
    }

    @Override
    public R<List<UserBasicDTO>> listInfo(UserQueryParamDTO queryParam) {
        if (Tools.Str.isNotBlank(queryParam.getDeptId())) {
            List<String> deptIds = Boolean.TRUE.equals(queryParam.getCurrentDeptGradeOnly()) ?
                    Tools.Coll.newList(queryParam.getDeptId()) :
                    this.userDeptApi.loadSubAllId(queryParam.getDeptId());
            if (Tools.Coll.isBlank(deptIds)) {
                return R.ok(Collections.emptyList());
            }
            List<String> userIds = this.userDeptRepo.listObjs(Wrappers.<UserDeptDO>lambdaQuery()
                    .select(UserDeptDO::getUserId)
                    .in(UserDeptDO::getDeptId, deptIds));
            if (Tools.Coll.isBlank(userIds)) {
                return R.ok(Collections.emptyList());
            }
            Tools.Coll.addAll(queryParam.getUserIds(), userIds);
        }
        return R.ok(this.converter.modelInfoConvert(this.repo.list(UserRepository.createWrapper(queryParam))));
    }

    @Override
    public R<List<UserSocialDTO>> listSocialById(String id) {
        UserDO entity = this.repo.getById(id);
        if (null == entity) {
            return R.ok(Collections.emptyList());
        }
        List<UserSocialDO> userSocialEntities = this.userSocialRepo.list(Wrappers.<UserSocialDO>lambdaQuery().eq(UserSocialDO::getUserId, id));
        return R.ok(Tools.Coll.convertList(userSocialEntities, userSocialEntity -> {
            UserSocialDTO userSocial = Tools.Bean.copy(userSocialEntity, UserSocialDTO.class);
            userSocial.setUserName(entity.getName());
            if (null != userSocial.getSocialType()) {
                userSocial.setSocialTypeName(userSocial.getSocialType().description);
            }
            return userSocial;
        }));
    }

    @Override
    public R<List<String>> listRoleIdByUserId(String userId) {
        return R.ok(this.userRoleRepo.listObjs(Wrappers.<UserRoleDO>lambdaQuery().select(UserRoleDO::getRoleId).eq(UserRoleDO::getUserId, userId)));
    }

    @Override
    public R<List<UserBasicDTO>> listInfoByRoleIds(BatchParam<String> roleIds) {
        if (Tools.Coll.isBlank(roleIds.getList())) {
            return R.ok(Collections.emptyList());
        }
        List<String> userIds = this.userRoleRepo.listObjs(Wrappers.<UserRoleDO>lambdaQuery()
                .select(UserRoleDO::getUserId)
                .in(UserRoleDO::getRoleId, roleIds.getList()));
        if (Tools.Coll.isBlank(userIds)) {
            return R.ok(Collections.emptyList());
        }
        return R.ok(this.converter.modelInfoConvert(this.repo.listByIds(userIds)));
    }

    @Override
    public R<List<UserBasicDTO>> listInfoByPostIds(BatchParam<String> postIds) {
        if (Tools.Coll.isBlank(postIds.getList())) {
            return R.ok(Collections.emptyList());
        }
        List<String> userIds = this.userPostRepo.listObjs(Wrappers.<UserPostDO>lambdaQuery()
                .select(UserPostDO::getUserId)
                .in(UserPostDO::getPostId, postIds.getList())
                .eq(UserPostDO::getTenantCode, Me.tenantCode()));
        if (Tools.Coll.isBlank(userIds)) {
            return R.ok(Collections.emptyList());
        }
        return R.ok(this.converter.modelInfoConvert(this.repo.listByIds(userIds)));
    }

    @Override
    public R<List<UserBasicDTO>> listInfoByMenuCodes(BatchParam<String> menuCodes) {
        List<String> menuIds = this.userMenuApi.loadIdByCodes(menuCodes.getList(), Me.tenantCode());
        List<String> roleIds = this.userRoleApi.loadIdByMenuIds(menuIds, Me.tenantCode());
        return R.ok(this.loadUserByRoleIds(roleIds));
    }

    @Override
    public R<List<UserBasicDTO>> listInfoByIds(BatchParam<String> ids) {
        if (Tools.Coll.isBlank(ids.getList())) {
            return R.ok(Collections.emptyList());
        }
        return R.ok(this.converter.modelInfoConvert(this.repo.listByIds(ids.getList())));
    }

    @Override
    public R<UserBasicDTO> getInfoById(String id) {
        UserDO entity = this.repo.getById(id);
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        return R.ok(this.converter.modelInfoConvert(entity));
    }

    @Override
    public R<UserBasicDTO> getInfoByCredential(String credentialName) {
        UserDO entity = this.repo.getOne(Wrappers.<UserDO>lambdaQuery()
                .eq(UserDO::getUsername, credentialName)
                .or()
                .eq(UserDO::getPhone, credentialName)
                .or()
                .eq(UserDO::getEmail, credentialName)
                .or()
                .eq(UserDO::getIdentityNo, credentialName));
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        return R.ok(this.converter.modelInfoConvert(entity));
    }

    @Override
    public R<UserBasicDTO> getInfoBySocial(UserSocialQueryParamDTO queryParam) {
        UserDO entity = this.repo.getById(this.userSocialRepo.getObj(Wrappers.<UserSocialDO>lambdaQuery()
                .select(UserSocialDO::getUserId)
                .eq(UserSocialDO::getSocialId, queryParam.getSocialId())
                .eq(UserSocialDO::getSocialNo, queryParam.getSocialNo()), String.class::cast));
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        return R.ok(this.converter.modelInfoConvert(entity));
    }

    @Override
    public R<UserBasicDTO> getLoginUser() {
        UserDO entity = this.repo.getById(Me.id());
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        return R.ok(this.converter.modelInfoConvert(entity));
    }

    @Override
    @OperationLog(value = "获取用户最近一次登录租户")
    public R<Object> getRecentTenantByLoginUser() {
        String tenantCode = this.userTenantRepo.getObj(Wrappers.<UserTenantDO>lambdaQuery()
                .select(UserTenantDO::getTenantCode)
                .eq(UserTenantDO::getRecentLogin, true), String.class::cast);
        UserTenantDTO tenant = this.userTenantApi.loadByCode(tenantCode);
        if (null == tenant || TenantStatusEnum.NORMAL.equals(tenant.getTenantStatus())) {
            return R.ok();
        }
        return R.ok(tenant);
    }

    @Override
    @OperationLog(value = "记录用户登录租户")
    public R<Object> rememberUserLoginTenant(String tenantCode) {
        List<UserTenantDO> userTenantEntities = this.userTenantRepo.list(Wrappers.<UserTenantDO>lambdaQuery()
                .eq(UserTenantDO::getUserId, Me.id()));
        if (Tools.Coll.isBlank(userTenantEntities)) {
            return R.ok();
        }
        this.userTenantRepo.updateBatchById(Tools.Coll.traverse(userTenantEntities, entity -> {
            entity.setRecentLogin(Tools.Str.equals(tenantCode, entity.getTenantCode()));
            return entity;
        }));
        return R.ok();
    }

    @Override
    public R<List<UserCategoryDTO>> listCategoryByLoginUser() {
        return R.ok(Tools.Array.convertList(UserCategoryEnum.values(), value -> {
            switch (value) {
                case PLATFORM_ADMINISTRATOR -> {
                    return Me.categoryCodes().contains(UserCategoryEnum.PLATFORM_ADMINISTRATOR.name()) ||
                            Me.categoryCodes().contains(UserCategoryEnum.TENANT_ADMINISTRATOR.name()) ||
                            Me.categoryCodes().contains(UserCategoryEnum.NORMAL_USER.name());
                }
                case TENANT_ADMINISTRATOR -> {
                    return Me.categoryCodes().contains(UserCategoryEnum.TENANT_ADMINISTRATOR.name());
                }
                default -> {
                    return true;
                }
            }
        }, value -> {
            UserCategoryDTO category = new UserCategoryDTO();
            category.setCategoryCode(value);
            category.setCategoryName(value.description);
            return category;
        }));
    }

    public void insertUserDept(String userId, List<String> deptIds) {
        if (Tools.Coll.isBlank(deptIds)) {
            return;
        }
        this.userDeptRepo.saveBatch(Tools.Coll.convertList(deptIds, Tools.Str::isNotBlank, deptId -> {
            UserDeptDO userDeptEntity = new UserDeptDO();
            userDeptEntity.setId(Tools.Id.uuid());
            userDeptEntity.setUserId(userId);
            userDeptEntity.setDeptId(deptId);
            userDeptEntity.setTenantCode(Me.tenantCode());
            return userDeptEntity;
        }));
    }

    private void insertUserPost(String userId, List<String> postIds) {
        this.userPostRepo.saveBatch(Tools.Coll.convertList(postIds,
                Tools.Str::isNotBlank,
                postId -> {
                    UserPostDO userPostEntity = new UserPostDO();
                    userPostEntity.setId(Tools.Id.uuid());
                    userPostEntity.setUserId(userId);
                    userPostEntity.setPostId(postId);
                    userPostEntity.setTenantCode(Me.tenantCode());
                    return userPostEntity;
                }));
    }

    private void insertUserRole(String userId, List<String> roleIds) {
        this.userRoleRepo.saveBatch(Tools.Coll.convertList(roleIds,
                Tools.Str::isNotBlank,
                roleId -> {
                    UserRoleDO userRoleEntity = new UserRoleDO();
                    userRoleEntity.setId(Tools.Id.uuid());
                    userRoleEntity.setUserId(userId);
                    userRoleEntity.setRoleId(roleId);
                    userRoleEntity.setTenantCode(Me.tenantCode());
                    userRoleEntity.setDomainCode(Me.domainCode());
                    userRoleEntity.setAppCode(Me.appCode());
                    return userRoleEntity;
                }));
    }

    private void insertUserTenant(String userId) {
        if (Tools.Str.isBlank(Me.tenantCode())) {
            return;
        }
        UserTenantDO entity = new UserTenantDO();
        entity.setId(Tools.Id.uuid());
        entity.setUserId(userId);
        entity.setTenantCode(Me.tenantCode());
        this.userTenantRepo.save(entity);
    }

    private void insertUserCategory(String userId) {
        UserCategoryDO entity = new UserCategoryDO();
        entity.setId(Tools.Id.uuid());
        entity.setUserId(userId);
        entity.setCategoryCode(DomainEnum.PLATFORM.name().equals(Me.domainCode()) ?
                UserCategoryEnum.PLATFORM_ADMINISTRATOR.name() :
                UserCategoryEnum.NORMAL_USER.name());
        entity.setTenantCode(Me.tenantCode());
        this.userCategoryRepo.save(entity);
    }

    private void updateUserDept(String userId, List<String> deptIds) {
        this.userDeptRepo.remove(Wrappers.<UserDeptDO>lambdaQuery()
                .eq(UserDeptDO::getUserId, userId)
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), UserDeptDO::getTenantCode, Me.tenantCode()));
        this.insertUserDept(userId, deptIds);
    }

    private void updateUserPost(String userId, List<String> postIds) {
        this.userPostRepo.remove(Wrappers.<UserPostDO>query().lambda()
                .eq(UserPostDO::getUserId, userId)
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), UserPostDO::getTenantCode, Me.tenantCode()));
        this.insertUserPost(userId, postIds);
    }

    private void updateUserRole(String userId, List<String> roleIds) {
        this.userRoleRepo.remove(Wrappers.<UserRoleDO>query().lambda()
                .eq(UserRoleDO::getUserId, userId)
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), UserRoleDO::getTenantCode, Me.tenantCode())
                .eq(Tools.Str.isNotBlank(Me.appCode()), UserRoleDO::getAppCode, Me.appCode()));
        this.insertUserRole(userId, roleIds);
    }

    private void updateUserCategory(String userId) {
        this.userCategoryRepo.remove(Wrappers.<UserCategoryDO>lambdaQuery().eq(UserCategoryDO::getUserId, userId));
        this.insertUserCategory(userId);
    }

    private void updateUser(UserBasicDTO user) {
        if (!this.repo.exists(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getId, user.getId()))) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        if (this.repo.exists(Wrappers.<UserDO>lambdaQuery().ne(UserDO::getId, user.getId()).eq(UserDO::getUsername, user.getUsername()))) {
            throw new FrameworkException(UserExceptionInfo.DUPLICATE_USERNAME);
        }
        if (this.repo.exists(Wrappers.<UserDO>lambdaQuery().ne(UserDO::getId, user.getId()).eq(UserDO::getPhone, user.getPhone()))) {
            throw new FrameworkException(UserExceptionInfo.DUPLICATE_PHONE);
        }
        this.repo.updateById(Tools.Bean.copy(user, UserDO.class));
    }

    private UserDO createUser(String userId, String username, String name, String password, Boolean adminUser) {
        UserDO entity = new UserDO();
        entity.setId(userId);
        entity.setUsername(username);
        entity.setName(name);
        entity.setNickname(name);
        entity.setPassword(password);
        entity.setPasswordModifyTime(DEFAULT_PASSWORD_MODIFY_TIME);
        entity.setPlatformUser(true);
        entity.setAdminUser(adminUser);
        return entity;
    }

    private UserCategoryDO createUserCategory(String userId, UserCategoryEnum category) {
        UserCategoryDO entity = new UserCategoryDO();
        entity.setId(Tools.Id.uuid());
        entity.setUserId(userId);
        entity.setCategoryCode(category.name());
        return entity;
    }

    private List<UserBasicDTO> loadUserByRoleIds(List<String> roleIds) {
        if (Tools.Coll.isBlank(roleIds)) {
            return new ArrayList<>();
        }
        List<String> userIds = this.userRoleRepo.listObjs(Wrappers.<UserRoleDO>lambdaQuery()
                .select(UserRoleDO::getUserId)
                .in(UserRoleDO::getRoleId, roleIds));
        if (Tools.Coll.isBlank(userIds)) {
            return new ArrayList<>();
        }
        return this.converter.modelInfoConvert(this.repo.listByIds(userIds));
    }

    @EventListener(SystemInitEvent.class)
    public void systemInitEventListener() {
        if (!this.repo.exists(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getUsername, ADMIN_USER_NAME))) {
            UserDO newEntity = this.createUser(Tools.Id.uuid(), ADMIN_USER_NAME, "平台管理员", this.defaultEncodedPassword(), true);
            this.repo.save(newEntity);
            this.userCategoryRepo.save(this.createUserCategory(newEntity.getId(), UserCategoryEnum.PLATFORM_ADMINISTRATOR));
        }
        if (!this.userConfigRepo.exists(Wrappers.<UserConfigDO>lambdaQuery().eq(UserConfigDO::getType, UserConfigTypeEnum.FRONT_GRAY_SCALE.name()))) {
            UserConfigDO userConfigEntity = new UserConfigDO();
            userConfigEntity.setId(Tools.Id.uuid());
            userConfigEntity.setUserId("global");
            userConfigEntity.setGlobal(true);
            userConfigEntity.setType(UserConfigTypeEnum.FRONT_GRAY_SCALE.name());
            userConfigEntity.setValue("0");
            this.userConfigRepo.save(userConfigEntity);
        }
    }

    @EventListener
    public void tenantInitEventListener(TenantInitEvent event) {
        String username = Tools.Str.format("{}_{}", event.getCode(), ADMIN_USER_NAME);
        if (!this.repo.exists(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getUsername, username))) {
            UserDO newEntity = this.createUser(Tools.Id.uuid(), username, "系统管理员", this.defaultEncodedPassword(), true);
            this.repo.save(newEntity);
            this.userCategoryRepo.save(this.createUserCategory(newEntity.getId(), UserCategoryEnum.TENANT_ADMINISTRATOR));
            UserTenantDO userTenantEntity = new UserTenantDO();
            userTenantEntity.setId(Tools.Id.uuid());
            userTenantEntity.setUserId(newEntity.getId());
            userTenantEntity.setTenantCode(event.getCode());
            userTenantEntity.setRecentLogin(false);
            this.userTenantRepo.save(userTenantEntity);
        }
    }

    @EventListener
    public void tenantDeleteEventListener(TenantDeleteEvent event) {
        this.repo.remove(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getUsername, Tools.Str.format("{}_{}", event.getCode(), ADMIN_USER_NAME)));
        this.userDeptRepo.remove(Wrappers.<UserDeptDO>lambdaQuery().eq(UserDeptDO::getTenantCode, event.getCode()));
        this.userPostRepo.remove(Wrappers.<UserPostDO>lambdaQuery().eq(UserPostDO::getTenantCode, event.getCode()));
        this.userRoleRepo.remove(Wrappers.<UserRoleDO>lambdaQuery().eq(UserRoleDO::getTenantCode, event.getCode()));
        this.userTenantRepo.remove(Wrappers.<UserTenantDO>lambdaQuery().eq(UserTenantDO::getTenantCode, event.getCode()));
        this.userConfigRepo.remove(Wrappers.<UserConfigDO>lambdaQuery().eq(UserConfigDO::getTenantCode, event.getCode()));
    }
}
